#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "gc.h"
#include "mem.h"
#include "eval.h"
#include "rmath.h"
#include "async.h"
#include "thread.h"
#include "redobject.h"
#include "redintobject.h"
#include "redmapobject.h"
#include "redboolobject.h"
#include "redcodeobject.h"
#include "redfileobject.h"
#include "rednullobject.h"
#include "redarrayobject.h"
#include "redblockobject.h"
#include "redfloatobject.h"
#include "redframeobject.h"
#include "redsuperobject.h"
#include "redtraceobject.h"
#include "redtupleobject.h"
#include "redmoduleobject.h"
#include "redstringobject.h"
#include "redfunctionobject.h"
#include "redcfunctionobject.h"
#include "redexceptionobject.h"
#include "redboundmethodobject.h"

typedef struct _exit_fn_t exit_fn_t;
typedef struct _exit_fn_t
{
	exit_fn_t		*prev;
	exit_fn_t		*next;
	void			*args;
	RedExitFunction	 func;
} exit_fn_t;

/* public variables */
int Red_Argc;
char **Red_Argv;

/* private variables */
static exit_fn_t exit_functions =
{
	.args = NULL,
	.func = NULL,
	.prev = &exit_functions,
	.next = &exit_functions,
};

#ifdef DEBUG

typedef struct _object_state_t
{
	int			count;
	RedObject	objects;
} object_state_t;

static int object_mutex = 0;
static object_state_t object_state =
{
	.count = 0,
	.objects =
	{
		.ob_prev = &(object_state.objects),
		.ob_next = &(object_state.objects),
	},
};

#define queue_unlock()	__sync_lock_release(&object_mutex)
#define queue_protect()	while (__sync_lock_test_and_set(&object_mutex, 1)) while (object_mutex)

static void object_queue(RedObject *self)
{
	queue_protect();
	self->ob_next = &(object_state.objects);
	self->ob_prev = object_state.objects.ob_prev;
	object_state.objects.ob_prev->ob_next = self;
	object_state.objects.ob_prev = self;
	object_state.count++;
	queue_unlock();
}

static void object_dequeue(RedObject *self)
{
	queue_protect();
	object_state.count--;
	self->ob_prev->ob_next = self->ob_next;
	self->ob_next->ob_prev = self->ob_prev;
	queue_unlock();
}

#undef queue_unlock
#undef queue_protect
#endif

/* built-in methods for both `type` and `object`, actually all types */

static uint32_t default_hash(RedObject *self)
{
	return rmath_hash_i64((int64_t)self);
}

static void default_release(RedObject *self)
{
	RedObject_GC_Remove(self);
	Red_MemoryState.objects -= self->ob_type->tp_size;

#ifdef DEBUG
	/* remove from object list */
	object_dequeue(self);
#endif

	Red_UNREF(self->ob_type);
	Red_UNREF(self->ob_attrs);
	RedObject_GC_Free(self);
}

static RedObject *default_delattr(RedObject *self, RedObject *attr)
{
	if (!RedString_Exact(attr))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "Attribute name must be 'string'");
		return NULL;
	}

	if (self->ob_attrs &&
		(!(self->ob_flags & RED_OBJECT_STATIC) ||
		 (self->ob_type->tp_flags & RED_TYPE_ATTR_MUTABLE)))
	{
		if (RedMap_DelString(self->ob_attrs, attr))
			Red_Return_Null();

		RedException_ThrowFormat(&RedExc_AttributeError, "'%s' object doesn't have attribute '%s'", RedObject_ClassName(self), RedString_Buffer(attr));
		return NULL;
	}

	RedException_ThrowFormat(&RedExc_AttributeError, "Cannot '__delattr__' for '%s' objects", RedObject_ClassName(self));
	return NULL;
}

static RedObject *default_getattr(RedObject *self, RedObject *attr)
{
	if (!RedString_Exact(attr))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "Attribute name must be 'string'");
		return NULL;
	}

	/* `map()` object doesn't have attribude `ob_attrs` */
	RedObject *result = self->ob_attrs ? RedMap_GetString(self->ob_attrs, attr) : NULL;

	/* attribute found in instance level */
	if (result)
	{
		Red_REF(result);
		return result;
	}

	/* resolve attribute in class level */
	RedTypeObject *prev = NULL;
	RedTypeObject *type = self->ob_type;

	/* `object`->tp_parent == `object` */
	while (type != prev)
	{
		if ((result = RedMap_GetString(type->ob_head.ob_attrs, attr)))
		{
			/* increase it's ref-count since RedMap_Get doesn't do it */
			Red_REF(result);
			
			/* class level method must be bound with `self` */
			if (RedObject_Callable(result))
				result = RedBoundMethod_BindSelf(result, self);

			return result;
		}

		prev = type;
		type = type->tp_parent;
	}

	if (RedString_EqualsCString(attr, "__class__"))
	{
		Red_REF(self->ob_type);
		return (RedObject *)(self->ob_type);
	}

	RedException_ThrowFormat(&RedExc_AttributeError, "'%s' object doesn't have attribute '%s'", RedObject_ClassName(self), RedString_Buffer(attr));
	return NULL;
}

static RedObject *default_setattr(RedObject *self, RedObject *attr, RedObject *value)
{
	if (!RedString_Exact(attr))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "Attribute name must be 'string'");
		return NULL;
	}

	if (self->ob_attrs &&
		(!(self->ob_flags & RED_OBJECT_STATIC) &&
		  (self->ob_type->tp_flags & RED_TYPE_ATTR_MUTABLE)))
	{
		RedMap_PutString(self->ob_attrs, attr, value);
		Red_Return_Null();
	}

	RedException_ThrowFormat(&RedExc_AttributeError, "Cannot '__setattr__' for '%s' objects", RedObject_ClassName(self));
	return NULL;
}

static RedObject *default_dir(RedObject *self)
{
	/* `map()` object has no attribtues */
	return self->ob_attrs ? RedMap_Keys(self->ob_attrs) : RedTuple_FromLength(0);
}

static RedObject *default_eq(RedObject *self, RedObject *other)
{
	if (self == other)
		Red_Return_True();
	else
		Red_Return_False();
}

static RedObject *default_neq(RedObject *self, RedObject *other)
{
	if (self != other)
		Red_Return_True();
	else
		Red_Return_False();
}

static RedObject *default_compare(RedObject *self, RedObject *other)
{
	if (self == other)
		return RedInt_FromInt(0);
	else if ((uintptr_t)self > (uintptr_t)other)
		return RedInt_FromInt(1);
	else
		return RedInt_FromInt(-1);
}

/* methods specially for `type` */

static RedObject *resolve_method(RedObject *self, const char *name)
{
	/* wrap C-style name into RedObject */
	RedObject *attr = RedString_FromCString(name);

	/* resolve attribute in class level */
	RedTypeObject *prev = NULL;
	RedTypeObject *type = self->ob_type;

	/* `object`->tp_parent == `object` */
	while (type != prev)
	{
		RedObject *method = RedMap_GetString(type->ob_head.ob_attrs, attr);

		/* class level method must be bound with `self` */
		if (method && RedObject_Callable(method))
		{
			Red_REF(method);
			Red_UNREF(attr);
			return RedBoundMethod_BindSelf(method, self);
		}

		Red_UNREF(method);
		prev = type;
		type = type->tp_parent;
	}

	Red_UNREF(attr);
	return NULL;
}

static char class_init(RedObject *self, RedObject *args)
{
	/* invoke object's init direcly cause initializer of all custom class are identically the same */
	if (!RedType_Object.tp_init(self, args))
		return 0;

	if (!RedTuple_Exact(args))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'tuple' expected for 'args' argument");
		return 0;
	}

	/* looking for __init__ constructor */
	RedObject *ctor = resolve_method(self, "__init__");

	/* doesn't have a constructor, args must be empty */
	if (!ctor)
	{
		if (RedTuple_Length(args) == 0)
			return 1;

		RedException_ThrowFormat(&RedExc_ArgumentError, "Constructor of class '%s' takes no arguments", RedObject_ClassName(self));
		return 0;
	}

	/* call the constructor */
	RedObject *result = RedObject_Invoke(ctor, args);

	/* release after invoking */
	Red_UNREF(ctor);

	/* if the constructor fails */
	if (!result)
		return 0;

	Red_UNREF(result);
	return 1;
}

static void class_clear(RedObject *self)
{
	/* never clears `self->ob_type` */
	Red_UNREF(self->ob_attrs);
}

static void class_traverse(RedObject *self, tp_visit_fn_t visit, void *args)
{
	Red_VISIT(self->ob_type);
	Red_VISIT(self->ob_attrs);
}

static inline RedObject *apply_method0(RedObject *self, const char *method)
{
	RedObject *callable = resolve_method(self, method);

	if (!callable)
		return NULL;

	RedObject *args = RedTuple_FromLength(0);
	RedObject *result = RedObject_Invoke(callable, args);

	Red_UNREF(args);
	Red_UNREF(callable);
	return result;
}

static inline RedObject *apply_method1(RedObject *self, const char *method, RedObject *arg0)
{
	RedObject *callable = resolve_method(self, method);

	if (!callable)
		return NULL;

	RedObject *args = RedTuple_FromObjects(1, arg0);
	RedObject *result = RedObject_Invoke(callable, args);

	Red_UNREF(args);
	Red_UNREF(callable);
	return result;
}

static inline RedObject *apply_method2(RedObject *self, const char *method, RedObject *arg0, RedObject *arg1)
{
	RedObject *callable = resolve_method(self, method);

	if (!callable)
		return NULL;

	RedObject *args = RedTuple_FromObjects(2, arg0, arg1);
	RedObject *result = RedObject_Invoke(callable, args);

	Red_UNREF(args);
	Red_UNREF(callable);
	return result;
}

static RedObject *class_delattr(RedObject *self, RedObject *attr)
{
	RedObject *result;
	return (result = apply_method1(self, "__delattr__", attr)) ? result : default_delattr(self, attr);
}

static RedObject *class_getattr(RedObject *self, RedObject *attr)
{
	RedObject *result;
	return (result = apply_method1(self, "__getattr__", attr)) ? result : default_getattr(self, attr);
}

static RedObject *class_setattr(RedObject *self, RedObject *attr, RedObject *value)
{
	RedObject *result;
	return (result = apply_method2(self, "__setattr__", attr, value)) ? result : default_setattr(self, attr, value);
}

static RedObject *class_dir(RedObject *self)
{
	RedObject *result;
	return (result = apply_method0(self, "__dir__")) ? result : default_dir(self);
}

static RedObject *class_str(RedObject *self)
{
	RedObject *result;
	return (result = apply_method0(self, "__str__")) ? result :
		   (result = apply_method0(self, "__repr__")) ? result :
		   RedString_FromCFormat("<instance of class '%s' at %p>", RedObject_ClassName(self), self);
}

static RedObject *class_repr(RedObject *self)
{
	RedObject *result;
	return (result = apply_method0(self, "__repr__")) ? result :
		RedString_FromCFormat("<instance of class '%s' at %p>", RedObject_ClassName(self), self);
}

static RedObject *class_eq(RedObject *self, RedObject *other)
{
	RedObject *result;
	return (result = apply_method1(self, "__eq__", other)) ? result : default_eq(self, other);
}

static RedObject *class_neq(RedObject *self, RedObject *other)
{
	RedObject *result;
	return (result = apply_method1(self, "__neq__", other)) ? result : default_neq(self, other);
}

static RedObject *class_compare(RedObject *self, RedObject *other)
{
	RedObject *result;
	return (result = apply_method1(self, "__compare__", other)) ? result : default_compare(self, other);
}

static char type_init(RedObject *self, RedObject *args)
{
	/* class inheritance */
	RedObject *name = NULL;
	RedTypeObject *super = &RedType_Object;

#ifdef DEBUG
	/* queue into object state */
	object_queue(self);
#endif

	/* update memory usage info */
	Red_MemoryState.objects += self->ob_type->tp_size;

	/* custom class */
	if (args)
	{
		if (!RedTuple_Exact(args))
		{
			RedException_ThrowFormat(&RedExc_TypeError, "'tuple' expected for 'args' argument");
			return 0;
		}

		/* type accepts 1 ~ 2 arguments */
		if (RedTuple_Length(args) < 1 || RedTuple_Length(args) > 2)
		{
			RedException_ThrowFormat(&RedExc_ArgumentError, "'type' constructor accepts 1 ~ 2 arguments, but %d given", RedTuple_Length(args));
			return 0;
		}

		/* type(<name>) */
		name = RedTuple_Items(args)[0];

		/* class name must be string */
		if (!RedString_Exact(name))
		{
			RedException_ThrowFormat(&RedExc_TypeError, "Class name must be string");
			return 0;
		}

		if (RedTuple_Length(args) == 2)
		{
			/* type(<name>, <super-class>) */
			super = (RedTypeObject *)RedTuple_Items(args)[1];

			/* super-class must be a class */
			if (super->ob_head.ob_type != &RedType_Type)
			{
				RedException_ThrowFormat(&RedExc_TypeError, "Super class must be a class");
				return 0;
			}

			/* cannot inherit from built-in classes */
			if (super->ob_head.ob_flags & RED_OBJECT_STATIC)
			{
				RedException_ThrowFormat(&RedExc_TypeError, "Cannot inherit from built-in classes");
				return 0;
			}
		}
	}

	RedTypeObject *type = (RedTypeObject *)self;

	/* we need to prepare the class manually */
	type->tp_name = "<class>";		/* fake, cause user must pass at least a string as its class name */
	type->tp_size = sizeof(RedObject);
	type->tp_flags = RED_TYPE_PREPARED | RED_TYPE_ATTR_MUTABLE;
	type->tp_parent = super;
	type->ob_head.ob_size = sizeof(RedTypeObject);

	/* basic methods, other methods are not interested*/
	type->tp_init = class_init;
	type->tp_hash = default_hash;
	type->tp_clear = class_clear;
	type->tp_alloc = RedObject_Create;
	type->tp_release = default_release;
	type->tp_traverse = class_traverse;

	type->tp_delattr = class_delattr;
	type->tp_getattr = class_getattr;
	type->tp_setattr = class_setattr;

	type->tp_dir = class_dir;
	type->tp_str = class_str;
	type->tp_repr = class_repr;

	type->tp_eq = class_eq;
	type->tp_neq = class_neq;
	type->tp_compare = class_compare;

	/* initialize this type */
	RedType_Init((RedTypeObject *)self);

	/* add it's attribute map to GC track list */
	RedObject_GC_Insert(self->ob_attrs);

	/* internal call */
	if (!args)
		return 1;

	/* reset class name to `name` */
	RedObject *key = RedString_FromCString("__name__");

	RedMap_PutString(self->ob_attrs, key, name);
	Red_UNREF(key);
	return 1;
}

static void type_clear(RedObject *self)
{
	if (self->ob_attrs &&
		(!(self->ob_flags & RED_OBJECT_STATIC) &&
		  (self->ob_type->tp_flags & RED_TYPE_ATTR_MUTABLE)))
	{
		/* never clears `self->ob_type` */
		Red_UNREF(self->ob_attrs);
	}
}

static void type_traverse(RedObject *self, tp_visit_fn_t visit, void *args)
{
	if (self->ob_attrs &&
		(!(self->ob_flags & RED_OBJECT_STATIC) &&
		  (self->ob_type->tp_flags & RED_TYPE_ATTR_MUTABLE)))
	{
		Red_VISIT(self->ob_type);
		Red_VISIT(self->ob_attrs);
	}
}

static RedObject *type_repr(RedObject *self)
{
	RedObject *attr = RedString_FromCString("__name__");
	RedObject *name = RedMap_GetString(self->ob_attrs, attr);
	const char *class = name ? RedString_Exact(name) ? RedString_Buffer(name) : NULL : ((RedTypeObject *)self)->tp_name;

	if (class)
	{
		Red_UNREF(attr);
		return RedString_FromCFormat("<class '%s' at %p>", class, self);
	}

	Red_UNREF(attr);
	RedException_ThrowFormat(&RedExc_TypeError, "'string' expected for '__name__'");
	return NULL;
}

static RedObject *type_invoke(RedObject *self, RedObject *args)
{
	RedObject *result = ((RedTypeObject *)self)->tp_alloc((RedTypeObject *)self, args);

	if (!result)
		return NULL;

	RedObject_GC_Insert(result);
	return result;
}

/* methods specially for `object` */

static char object_init(RedObject *self, RedObject *args)
{
#ifdef DEBUG
	/* MUST be queued before the map was created */
	object_queue(self);
#endif

	/* only mutable objects need `__attrs__` */
	if (self->ob_type->tp_flags & RED_TYPE_ATTR_MUTABLE)
		self->ob_attrs = RedMap_CreateStringKey();

	/* dynamic class */
	if (!(self->ob_type->ob_head.ob_flags & RED_OBJECT_STATIC))
		RedObject_GC_Insert(self->ob_attrs);

	/* update memory usage info */
	Red_MemoryState.objects += self->ob_type->tp_size;
	return 1;
}

static void object_clear(RedObject *self)
{
	/* nothing to clear */
}

static void object_traverse(RedObject *self, tp_visit_fn_t visit, void *args)
{
	/* nothing to visit */
}

static RedObject *object_repr(RedObject *self)
{
	return RedString_FromCFormat("<'%s' object at %p>", RedObject_ClassName(self), self);
}

RedTypeObject RedType_Type =
{
	RedType_Mutable_INIT("type", RedTypeObject, RedType_Type)

	.tp_hash		= default_hash,
	.tp_alloc		= RedObject_Create,

	.tp_init		= type_init,
	.tp_clear		= type_clear,
	.tp_release		= default_release,
	.tp_traverse	= type_traverse,

	.tp_delattr		= default_delattr,
	.tp_getattr		= default_getattr,
	.tp_setattr		= default_setattr,

	.tp_dir			= default_dir,
	.tp_str			= type_repr,
	.tp_repr		= type_repr,
	.tp_invoke		= type_invoke,

	.tp_eq			= default_eq,
	.tp_neq			= default_neq,
	.tp_compare		= default_compare,
};

/* the base type of everything except `type` and `root class` */
RedTypeObject RedType_Object =
{
	RedType_INIT("object", RedObject, RedType_Object)

	.tp_hash		= default_hash,
	.tp_alloc		= RedObject_Create,

	.tp_init		= object_init,
	.tp_clear		= object_clear,
	.tp_release		= default_release,
	.tp_traverse	= object_traverse,

	.tp_delattr		= default_delattr,
	.tp_getattr		= default_getattr,
	.tp_setattr		= default_setattr,

	.tp_dir			= default_dir,
	.tp_str			= object_repr,
	.tp_repr		= object_repr,

	.tp_eq			= default_eq,
	.tp_neq			= default_neq,
	.tp_compare		= default_compare,
};

/* object manager */

char Red_Init(int argc, char **argv)
{
	/* initialize argc and argv */
	Red_Argc = argc;
	Red_Argv = argv;

	/* initialize GC and memory pool */
	Red_InitMemoryPool();
	Red_InitGarbageCollector();

	/* prepare types */
	if (!RedType_Prepare(&RedType_Map)) return 0;
	if (!RedType_Prepare(&RedType_Int)) return 0;
	if (!RedType_Prepare(&RedType_Bool)) return 0;
	if (!RedType_Prepare(&RedType_Code)) return 0;
	if (!RedType_Prepare(&RedType_File)) return 0;
	if (!RedType_Prepare(&RedType_Null)) return 0;
	if (!RedType_Prepare(&RedType_Type)) return 0;
	if (!RedType_Prepare(&RedType_Array)) return 0;
	if (!RedType_Prepare(&RedType_Block)) return 0;
	if (!RedType_Prepare(&RedType_Float)) return 0;
	if (!RedType_Prepare(&RedType_Frame)) return 0;
	if (!RedType_Prepare(&RedType_Super)) return 0;
	if (!RedType_Prepare(&RedType_Trace)) return 0;
	if (!RedType_Prepare(&RedType_Tuple)) return 0;
	if (!RedType_Prepare(&RedType_Module)) return 0;
	if (!RedType_Prepare(&RedType_Object)) return 0;
	if (!RedType_Prepare(&RedType_String)) return 0;
	if (!RedType_Prepare(&RedType_Function)) return 0;
	if (!RedType_Prepare(&RedType_CFunction)) return 0;
	if (!RedType_Prepare(&RedType_BoundMethod)) return 0;

	/* prepare base exception */
	if (!RedType_Prepare(&RedExc_Exception)) return 0;

	/* prepare special exceptions */
	if (!RedType_Prepare(&RedExc_SystemExit)) return 0;
	if (!RedType_Prepare(&RedExc_KeyboardInterrupt)) return 0;

	/* prepare generic exceptions */
	if (!RedType_Prepare(&RedExc_IOError)) return 0;
	if (!RedType_Prepare(&RedExc_OSError)) return 0;
	if (!RedType_Prepare(&RedExc_NameError)) return 0;
	if (!RedType_Prepare(&RedExc_TypeError)) return 0;
	if (!RedType_Prepare(&RedExc_IndexError)) return 0;
	if (!RedType_Prepare(&RedExc_ValueError)) return 0;
	if (!RedType_Prepare(&RedExc_FormatError)) return 0;
	if (!RedType_Prepare(&RedExc_ImportError)) return 0;
	if (!RedType_Prepare(&RedExc_MemoryError)) return 0;
	if (!RedType_Prepare(&RedExc_CompileError)) return 0;
	if (!RedType_Prepare(&RedExc_RuntimeError)) return 0;
	if (!RedType_Prepare(&RedExc_ArgumentError)) return 0;
	if (!RedType_Prepare(&RedExc_InternalError)) return 0;
	if (!RedType_Prepare(&RedExc_AttributeError)) return 0;
	if (!RedType_Prepare(&RedExc_FloatPointError)) return 0;
	if (!RedType_Prepare(&RedExc_UnhashableError)) return 0;
	if (!RedType_Prepare(&RedExc_NotCallableError)) return 0;
	if (!RedType_Prepare(&RedExc_NotPrintableError)) return 0;
	if (!RedType_Prepare(&RedExc_RecursionTooDepthError)) return 0;

	/* initialize constant pools */
	if (!Red_InitStringPool()) return 0;
	if (!Red_InitIntegerPool()) return 0;

	/* initialize types */
	RedType_Init(&RedType_Map);
	RedType_Init(&RedType_Int);
	RedType_Init(&RedType_Bool);
	RedType_Init(&RedType_Code);
	RedType_Init(&RedType_File);
	RedType_Init(&RedType_Null);
	RedType_Init(&RedType_Type);
	RedType_Init(&RedType_Array);
	RedType_Init(&RedType_Block);
	RedType_Init(&RedType_Float);
	RedType_Init(&RedType_Frame);
	RedType_Init(&RedType_Super);
	RedType_Init(&RedType_Trace);
	RedType_Init(&RedType_Tuple);
	RedType_Init(&RedType_Module);
	RedType_Init(&RedType_Object);
	RedType_Init(&RedType_String);
	RedType_Init(&RedType_Function);
	RedType_Init(&RedType_CFunction);
	RedType_Init(&RedType_BoundMethod);

	/* initialize base exception */
	RedType_Init(&RedExc_Exception);

	/* initialize special exceptions */
	RedType_Init(&RedExc_SystemExit);
	RedType_Init(&RedExc_KeyboardInterrupt);

	/* initialize generic exceptions */
	RedType_Init(&RedExc_IOError);
	RedType_Init(&RedExc_OSError);
	RedType_Init(&RedExc_NameError);
	RedType_Init(&RedExc_TypeError);
	RedType_Init(&RedExc_IndexError);
	RedType_Init(&RedExc_ValueError);
	RedType_Init(&RedExc_FormatError);
	RedType_Init(&RedExc_ImportError);
	RedType_Init(&RedExc_MemoryError);
	RedType_Init(&RedExc_CompileError);
	RedType_Init(&RedExc_RuntimeError);
	RedType_Init(&RedExc_ArgumentError);
	RedType_Init(&RedExc_InternalError);
	RedType_Init(&RedExc_AttributeError);
	RedType_Init(&RedExc_FloatPointError);
	RedType_Init(&RedExc_UnhashableError);
	RedType_Init(&RedExc_NotCallableError);
	RedType_Init(&RedExc_NotPrintableError);
	RedType_Init(&RedExc_RecursionTooDepthError);

	/* initialize thread manager */
	if (!Red_InitThreadManager()) return 0;

	/* special methods in `object` namespace */
	if (!RedObject_AddBinary(&(RedType_Object.ob_head), "__delattr__", RedType_Object.tp_delattr)) return 0;
	if (!RedObject_AddBinary(&(RedType_Object.ob_head), "__getattr__", RedType_Object.tp_getattr)) return 0;
	if (!RedObject_AddTernary(&(RedType_Object.ob_head), "__setattr__", RedType_Object.tp_setattr)) return 0;

	/* initialize defered function queue */
	return Red_InitAsyncQueue();
}

void Red_Cleanup(void)
{
	/* clear exception */
	RedException_Clear();

	/* release eval engine */
	Red_FreeAsyncQueue();

	/* release thread manager */
	Red_FreeThreadManager();

	/* run all exit functions */
	exit_fn_t *node = exit_functions.next;

	while (node != &exit_functions)
	{
		exit_fn_t *next = node->next;

		node->func(node->next);
		RedMem_Free(node);
		node = next;
	}

	/* perform a full collect */
	RedObject_GC_Collect();

	/* cleanup built-in types */
	RedType_Cleanup(&RedType_Map);
	RedType_Cleanup(&RedType_Int);
	RedType_Cleanup(&RedType_Bool);
	RedType_Cleanup(&RedType_Code);
	RedType_Cleanup(&RedType_File);
	RedType_Cleanup(&RedType_Null);
	RedType_Cleanup(&RedType_Type);
	RedType_Cleanup(&RedType_Array);
	RedType_Cleanup(&RedType_Block);
	RedType_Cleanup(&RedType_Float);
	RedType_Cleanup(&RedType_Frame);
	RedType_Cleanup(&RedType_Super);
	RedType_Cleanup(&RedType_Trace);
	RedType_Cleanup(&RedType_Tuple);
	RedType_Cleanup(&RedType_Module);
	RedType_Cleanup(&RedType_Object);
	RedType_Cleanup(&RedType_String);
	RedType_Cleanup(&RedType_Function);
	RedType_Cleanup(&RedType_CFunction);
	RedType_Cleanup(&RedType_BoundMethod);

	/* clean up built-in base exception */
	RedType_Cleanup(&RedExc_Exception);

	/* clean up built-in special exceptions */
	RedType_Cleanup(&RedExc_SystemExit);
	RedType_Cleanup(&RedExc_KeyboardInterrupt);

	/* clean up built-in generic exceptions */
	RedType_Cleanup(&RedExc_IOError);
	RedType_Cleanup(&RedExc_OSError);
	RedType_Cleanup(&RedExc_NameError);
	RedType_Cleanup(&RedExc_TypeError);
	RedType_Cleanup(&RedExc_IndexError);
	RedType_Cleanup(&RedExc_ValueError);
	RedType_Cleanup(&RedExc_FormatError);
	RedType_Cleanup(&RedExc_ImportError);
	RedType_Cleanup(&RedExc_MemoryError);
	RedType_Cleanup(&RedExc_CompileError);
	RedType_Cleanup(&RedExc_RuntimeError);
	RedType_Cleanup(&RedExc_ArgumentError);
	RedType_Cleanup(&RedExc_InternalError);
	RedType_Cleanup(&RedExc_AttributeError);
	RedType_Cleanup(&RedExc_FloatPointError);
	RedType_Cleanup(&RedExc_UnhashableError);
	RedType_Cleanup(&RedExc_NotCallableError);
	RedType_Cleanup(&RedExc_NotPrintableError);
	RedType_Cleanup(&RedExc_RecursionTooDepthError);

	/* release constant pool */
	Red_FreeStringPool();
	Red_FreeIntegerPool();

#ifdef DEBUG
	/* collect object status */
	if (object_state.count)
	{
		fprintf(stderr, "*** WARNING !! Objects not unref'ed are leaked.\n");
		for (RedObject *obj = object_state.objects.ob_next; obj != &(object_state.objects); obj = obj->ob_next)
			fprintf(stderr, "Object %p, ref-count %d, type %s\n", obj, obj->ob_ref, obj->ob_type->tp_name);
	}
#endif

	/* release GC and memory pool */
	Red_FreeGarbageCollector();
	Red_FreeMemoryPool();
}

void Red_ExitFunction(RedExitFunction fn, void *args)
{
	exit_fn_t *node = RedMem_Alloc(sizeof(exit_fn_t));

	node->func = fn;
	node->args = args;
	node->next = &exit_functions;
	node->prev = exit_functions.prev;
	exit_functions.prev->next = node;
	exit_functions.prev = node;
}

/* type protocols */

void RedType_Init(RedTypeObject *type)
{
	if (!(type->tp_flags & RED_TYPE_INITED))
	{
		/* this map is only for invoking method from script	 */
		type->ob_head.ob_attrs = RedMap_CreateStringKey();

		/* initialize built-in attributes */
		RedObject_AddObject(&(type->ob_head), "__name__", RedString_FromCString(type->tp_name));

		/* custom methods */
		for (RedMethodDesc *method = type->tp_methods; method && method->name && method->method; method++)
			RedObject_AddObject(&(type->ob_head), method->name, RedCFunction_WrapVarMethod(method->method));

		/* mark as initialized */
		type->tp_flags |= RED_TYPE_INITED;
	}
}

char RedType_Prepare(RedTypeObject *type)
{
	if (type->tp_flags & RED_TYPE_PREPARED)
		return 1;

	if (!type->tp_name ||
		!type->tp_parent ||
		(type->tp_size < sizeof(RedObject)))
		return 0;

	if (!type->tp_hash)
		type->tp_hash = default_hash;

	/****** slot initialization ******/

#define copy_slot(s)										\
	do														\
	{														\
		if (!(type->tp_##s))								\
		{													\
			RedTypeObject *_prev = NULL;					\
			RedTypeObject *_type = type;					\
															\
			while (!(_type->tp_##s) && (_type != _prev))	\
			{												\
				_prev = _type;								\
				_type = _type->tp_parent;					\
			}												\
															\
			type->tp_##s = _type->tp_##s;					\
		}													\
	} while (0)

	/* object basic methods */
	copy_slot(init);
	copy_slot(alloc);
	copy_slot(release);

	/* GC specific methods */
	copy_slot(clear);
	copy_slot(traverse);

	/* getters and setters */
	copy_slot(delattr);
	copy_slot(getattr);
	copy_slot(setattr);

	copy_slot(delitem);
	copy_slot(getitem);
	copy_slot(setitem);

	/* inspection methods */
	copy_slot(dir);
	copy_slot(len);
	copy_slot(str);
	copy_slot(repr);
	copy_slot(invoke);

	/* numeric methods */
	copy_slot(add);
	copy_slot(sub);
	copy_slot(mul);
	copy_slot(div);
	copy_slot(mod);
	copy_slot(pow);
	copy_slot(bit_or);
	copy_slot(bit_and);
	copy_slot(bit_xor);
	copy_slot(bit_not);
	copy_slot(lshift);
	copy_slot(rshift);

	copy_slot(aug_add);
	copy_slot(aug_sub);
	copy_slot(aug_mul);
	copy_slot(aug_div);
	copy_slot(aug_mod);
	copy_slot(aug_pow);
	copy_slot(aug_or);
	copy_slot(aug_and);
	copy_slot(aug_xor);
	copy_slot(aug_lshift);
	copy_slot(aug_rshift);

	/* boolean methods */
	copy_slot(bool_or);
	copy_slot(bool_and);
	copy_slot(bool_xor);
	copy_slot(bool_not);

	/* comparison methods */
	copy_slot(eq);
	copy_slot(le);
	copy_slot(ge);
	copy_slot(neq);
	copy_slot(leq);
	copy_slot(geq);
	copy_slot(compare);

	/* positive and negative methods */
	copy_slot(pos);
	copy_slot(neg);

#undef copy_slot

	type->tp_flags |= RED_TYPE_PREPARED;
	type->ob_head.ob_size = sizeof(RedTypeObject);
	return 1;
}

void RedType_Cleanup(RedTypeObject *type)
{
	/* clear it's attr map */
	Red_UNREF(type->ob_head.ob_attrs);
}

char RedType_IsInstance(RedObject *self, RedTypeObject *type)
{
	return RedType_IsSubclass(self->ob_type, type);
}

char RedType_IsSubclass(RedTypeObject *self, RedTypeObject *type)
{
	// `type` class doesn't have parent class
	if (self == &RedType_Type)
		return 0;

	while (self != type && self != &RedType_Object)
		self = self->tp_parent;

	return self == type;
}

uint32_t RedObject_Hash(RedObject *self)
{
	if (!(self->ob_flags & RED_OBJECT_HASHED))
	{
		if (!self->ob_type->tp_hash)
		{
			RedException_ThrowFormat(&RedExc_UnhashableError, "Unhashable type '%s'", RedObject_ClassName(self));
			return 0;
		}

		self->ob_hash = self->ob_type->tp_hash(self);
		self->ob_flags |= RED_OBJECT_HASHED;

		if (self->ob_hash == 0)
			self->ob_hash = 1;
	}

	return self->ob_hash;
}

const char *RedObject_ClassName(RedObject *self)
{
	RedObject *attr = RedString_FromCString("__name__");
	RedObject *result = RedMap_GetString(self->ob_type->ob_head.ob_attrs, attr);
	const char *classname = result ? RedString_Exact(result) ? RedString_Buffer(result) : "<'__name__' must be string>" : self->ob_type->tp_name;

	Red_UNREF(attr);
	return classname;
}

RedObject *RedObject_Create(RedTypeObject *type, RedObject *args)
{
	return RedObject_CreateVar(type, args, 0);
}

RedObject *RedObject_CreateVar(RedTypeObject *type, RedObject *args, size_t space)
{
	if (!(type->tp_flags & RED_TYPE_PREPARED))
	{
		RedException_ThrowFormat(&RedExc_InternalError, "Type '%s' was not prepared", type->tp_name);
		return NULL;
	}

	RedObject *result = RedObject_GC_Alloc(type->tp_size + space);

	if (!result)
		return NULL;

	memset(result, 0, type->tp_size);
	Red_REF(type);

	result->ob_ref = 1;
	result->ob_hash = 0;
	result->ob_type = type;
	result->ob_size = type->tp_size + space;

	if (type->tp_init && type->tp_init(result, args))
		return result;

	Red_UNREF(result);
	return NULL;
}

/* object protocols */

char RedObject_Print(RedObject *self)
{
	RedObject *str = RedObject_Str(self);

	if (!str)
		return 0;

	if (!RedString_Exact(str))
	{
		Red_UNREF(str);
		RedException_ThrowFormat(&RedExc_NotPrintableError, "'string' type is required for printing");
		return 0;
	}

	printf("%s", RedString_Buffer(str));
	Red_UNREF(str);
	return 1;
}

char RedObject_Callable(RedObject *self)
{
	if (RedFunction_Exact(self) ||
		RedCFunction_Exact(self) ||
		RedBoundMethod_Exact(self) ||
		self->ob_type->tp_invoke)
		return 1;

	RedObject *attr = RedString_FromCString("__invoke__");

	/* resolve attribute in class level */
	RedTypeObject *prev = NULL;
	RedTypeObject *type = self->ob_type;

	/* `object`->tp_parent == `object` */
	while (type != prev)
	{
		if (RedMap_HasKey(type->ob_head.ob_attrs, attr))
		{
			Red_UNREF(attr);
			return 1;
		}

		prev = type;
		type = type->tp_parent;
	}

	Red_UNREF(attr);
	return 0;
}

char RedObject_ReprEnter(RedObject *self)
{
	if (self->ob_flags & RED_OBJECT_IN_REPR)
		return 0;

	self->ob_flags |= RED_OBJECT_IN_REPR;
	return 1;
}

void RedObject_ReprLeave(RedObject *self)
{
	self->ob_flags &= ~RED_OBJECT_IN_REPR;
}

#define apply(slot, args, ...)															\
{																						\
	if (self->ob_type->tp_##slot)														\
		return self->ob_type->tp_##slot(self, ##__VA_ARGS__);							\
	else																				\
		return RedObject_InvokeMethodVa(self, "__"#slot"__", (args), ##__VA_ARGS__);	\
}

RedObject *RedObject_DelAttr(RedObject *self, RedObject *attr)						apply(delattr, 1, attr)
RedObject *RedObject_GetAttr(RedObject *self, RedObject *attr)						apply(getattr, 1, attr)
RedObject *RedObject_SetAttr(RedObject *self, RedObject *attr, RedObject *value)	apply(setattr, 2, attr, value)

RedObject *RedObject_DelItem(RedObject *self, RedObject *item)						apply(delitem, 1, item)
RedObject *RedObject_GetItem(RedObject *self, RedObject *item)						apply(getitem, 1, item)
RedObject *RedObject_SetItem(RedObject *self, RedObject *item, RedObject *value)	apply(setitem, 2, item, value)

RedObject *RedObject_Dir(RedObject *self)							apply(dir, 0)
RedObject *RedObject_Len(RedObject *self)							apply(len, 0)
RedObject *RedObject_Str(RedObject *self)							apply(str, 0)
RedObject *RedObject_Repr(RedObject *self)							apply(repr, 0)

RedObject *RedObject_Eq(RedObject *self, RedObject *other)			apply(eq, 1, other)
RedObject *RedObject_Geq(RedObject *self, RedObject *other)			apply(geq, 1, other)
RedObject *RedObject_Leq(RedObject *self, RedObject *other)			apply(leq, 1, other)
RedObject *RedObject_Neq(RedObject *self, RedObject *other)			apply(neq, 1, other)
RedObject *RedObject_Compare(RedObject *self, RedObject *other)		apply(compare, 1, other)

#undef apply

RedObject *RedObject_Invoke(RedObject *self, RedObject *args)
{
	if (RedFunction_Exact(self))		return RedFunction_Invoke(self, args);
	if (RedCFunction_Exact(self))		return RedCFunction_Invoke(self, args);
	if (RedBoundMethod_Exact(self))		return RedBoundMethod_Invoke(self, args);

	if (self->ob_type->tp_invoke)
		return self->ob_type->tp_invoke(self, args);

	/* resolve `__invoke__` magic method in class level only */
	RedObject *callable = resolve_method(self, "__invoke__");

	if (!callable)
	{
		RedException_ThrowFormat(&RedExc_NotCallableError, "'%s' object is not callable", RedObject_ClassName(self));
		return NULL;
	}

	/* invoke method */
	RedObject *result = RedObject_Invoke(callable, args);

	Red_UNREF(callable);
	return result;
}

RedObject *RedObject_InvokeMethod(RedObject *self, const char *method, RedObject *args)
{
	if (!RedTuple_Exact(args))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'tuple' expected for 'args' argument");
		return NULL;
	}

	/* resolve method in class level only */
	RedObject *callable = resolve_method(self, method);

	if (!callable)
	{
		RedException_ThrowFormat(&RedExc_AttributeError, "'%s' object doesn't have method '%s'", RedObject_ClassName(self), method);
		return NULL;
	}

	/* invoke method */
	RedObject *result = RedObject_Invoke(callable, args);

	Red_UNREF(callable);
	return result;
}

RedObject *RedObject_InvokeMethodVa(RedObject *self, const char *method, int count, ...)
{
	va_list args;
	RedObject *params = RedTuple_FromLength(count);

	if (!params)
		return NULL;

	va_start(args, count);

	for (int i = 0; i < count; i++)
	{
		RedTuple_Items(params)[i] = va_arg(args, RedObject *);
		Red_REF(RedTuple_Items(params)[i]);
	}

	RedObject *result = RedObject_InvokeMethod(self, method, params);

	va_end(args);
	Red_UNREF(params);
	return result;
}

RED_EXPORT char RedObject_AddConst(RedObject *self, const char *name, RedObject *value)
{
	if (!value)
	{
		RedException_ThrowFormat(&RedExc_ValueError, "Cannot add NULL to object");
		return 0;
	}

	RedObject *key = RedString_FromCString(name);

	if (!key)
		return 0;

	RedMap_PutString(self->ob_attrs, key, value);
	Red_UNREF(key);
	return 1;
}

RED_EXPORT char RedObject_AddObject(RedObject *self, const char *name, RedObject *value)
{
	char result = RedObject_AddConst(self, name, value);

	Red_UNREF(value);
	return result;
}

RED_EXPORT char RedObject_AddVarFunc(RedObject *self, const char *name, tp_unary_fn_t func)
{
	return func ? RedObject_AddObject(self, name, RedCFunction_WrapVarFunc(func)) : 0;
}

RED_EXPORT char RedObject_AddVarMethod(RedObject *self, const char *name, tp_binary_fn_t method)
{
	return method ? RedObject_AddObject(self, name, RedCFunction_WrapVarMethod(method)) : 0;
}

RED_EXPORT char RedObject_AddUnary(RedObject *self, const char *name, tp_unary_fn_t unary)
{
	return unary ? RedObject_AddObject(self, name, RedCFunction_WrapUnary(unary)) : 0;
}

RED_EXPORT char RedObject_AddBinary(RedObject *self, const char *name, tp_binary_fn_t binary)
{
	return binary ? RedObject_AddObject(self, name, RedCFunction_WrapBinary(binary)) : 0;
}

RED_EXPORT char RedObject_AddTernary(RedObject *self, const char *name, tp_ternary_fn_t ternary)
{
	return ternary ? RedObject_AddObject(self, name, RedCFunction_WrapTernary(ternary)) : 0;
}

RED_EXPORT char RedObject_AddNullary(RedObject *self, const char *name, tp_nullary_fn_t nullary)
{
	return nullary ? RedObject_AddObject(self, name, RedCFunction_WrapNullary(nullary)) : 0;
}
